using System;
using System.Collections.Generic;

namespace Elephant.Framework
{
    /// <summary>
    /// Represents any object containing specific functionalities.
    /// </summary>
    public class Entity : EntityComponentCollection, IEntity
    {
        string name;
        Guid guid;

        Entity parent;

        ComponentManager manager;
        
        public Entity(string name)
            : this(name, new EntityComponent[] { }) { }

        public Entity(string name, params EntityComponent[] components)
        {
            this.name = name;

            if (components.Length > 0) {
                for (int i = 0; i < components.Length; i++) {
                    Add(components[i]);
                }
            }
            
            guid = Guid.NewGuid();
        }
        
        protected override void OnComponentAdded(EntityComponentEventArgs e)
        {
            // can't circumvent doing this.. as using the factory wouldn't be good in every 
            // thinkable situation (ie. entity construction with passed component instances)
            EntityComponent ec = e.Component as EntityComponent;

            ec.Owner = this;

            base.OnComponentAdded(e);
        }

        #region IEntity Members

        /// <summary>
        /// Gets or sets the ComponentManager.
        /// </summary>
        public ComponentManager Manager
        {
            get
            {
                return manager;
            }
            set
            {
                manager = value;
            }
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        public string Name
        {
            get 
            { 
                return name; 
            }
        }

        /// <summary>
        /// Gets the Globally Unique ID
        /// </summary>
        public Guid ID
        {
            get 
            { 
                return guid; 
            }
        }

        // enabled == false, then record current state, then disable all components
        // enabled == true, then set all components to the recorded state
        EnabledState recordedState;

        /// <summary>
        /// Gets or sets whether the entity should be enabled or disabled. When disabling, the current state of 
        /// every component will be recorded and applied once enabling the entity again.
        /// </summary>
        public bool Enabled
        {
            get 
            {
                // garbage! each frame! -> wrong, each frame each component is checked - not the entity
                // solution: record enabled state upon adding components, and give it a property to tell if one component is enabled
                // although that gives problems: one could disable each component, and entity would still say it was enabled
                foreach (EntityComponent component in FindAll<EntityComponent>()) {
                    if (component.Enabled) {
                        return true;
                    }
                }

                return false;
            }
            set
            {
                List<EntityComponent> components = FindAll<EntityComponent>();

                if (value) {
                    // enable, recover previous state
                    if (recordedState != null) {
                        foreach (EntityComponent component in components) {
                            // cast to EntityComponent, to allow set operation on Enabled
                            EntityComponent ec = component as EntityComponent;
                            ec.Enabled = recordedState[component];
                        }
                    }

                    // clear the recorded state so we don't accidentally use this again
                    recordedState = null;
                } else {
                    // disable, record state
                    recordedState = new EnabledState(this);

                    foreach (EntityComponent component in components) {
                        // cast to EntityComponent, to allow set operation on Enabled
                        EntityComponent ec = component as EntityComponent;
                        ec.Enabled = false;
                    }
                }

            }
        }

        /// <summary>
        /// Gets or sets the parent. Assigning an entity to have itself as a parent is not possible.
        /// </summary>
        public Entity Parent
        {
            get 
            { 
                return parent; 
            }
            set
            {
                if (!IsReadOnly) {
                    if (value != this && parent != value) {
                        parent = value;

                        OnParentChanged(new EntityEventArgs(value));
                    }
                }
            }
        }

        /// <summary>
        /// Raised when the parent has changed.
        /// </summary>
        public event EventHandler<EntityEventArgs> ParentChanged;
        /// <summary>
        /// Raised when the parent has changed. Override this method to handle when the parent has changed.
        /// </summary>
        /// <param name="e"></param>
        private void OnParentChanged(EntityEventArgs e)
        {
            if (ParentChanged != null) {
                ParentChanged(this, e);
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing) {
                foreach (EntityComponent component in FindAll<EntityComponent>()) {
                    if (component is IDisposable) {
                        IDisposable disposable = component as IDisposable;
                        disposable.Dispose();
                    }
                }
            }
        }

        ~Entity()
        {
            this.Dispose(false);
        }

        #endregion
    }
}